package com.leyou.auth.service;

import com.leyou.auth.config.JwtProperties;
import com.leyou.auth.entity.ApplicationInfo;
import com.leyou.auth.mapper.ApplicationInfoMapper;
import com.leyou.common.auth.entity.Payload;
import com.leyou.common.auth.entity.UserInfo;
import com.leyou.common.auth.utils.JwtUtils;
import com.leyou.common.enums.ExceptionEnum;
import com.leyou.common.exception.LyException;
import com.leyou.common.utils.CookieUtils;
import com.leyou.user.client.UserClient;
import com.leyou.user.dto.UserDTO;
import com.leyou.auth.entity.AppInfo;
import lombok.extern.slf4j.Slf4j;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class AuthService {

    @Autowired
    private UserClient userClient;

    @Autowired
    private JwtProperties prop;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private ApplicationInfoMapper applicationInfoMapper;

    @Autowired
    private BCryptPasswordEncoder passwordEncoder;


    public void login(String username, String password, HttpServletResponse response) {
        try {
            // 授权： user-service微服务 : auth微服务调用user微服务
            UserDTO userDTO = userClient.queryUserByUsernameAndPassword(username, password);
            // 准备载荷
            UserInfo userInfo = new UserInfo();
            userInfo.setId(userDTO.getId());
            userInfo.setUsername(userDTO.getUsername());
            userInfo.setRole("guest");// 角色保留，这里没有做权限
            // 生成token
            String token = JwtUtils.generateTokenExpireInMinutes(userInfo, prop.getPrivateKey(), prop.getUser().getExpire());

            // 写入cookie
            CookieUtils.newCookieBuilder()
                    .name(prop.getUser().getCookieName())
                    .value(token)
                    .httpOnly(true)
                    .domain(prop.getUser().getCookieDomain())
                    .maxAge(prop.getUser().getMaxAge())
                    .path(prop.getUser().getPath())
                    .response(response)
                    .build();
        } catch (Exception e) {
            log.error("用户登录失败",e);
            throw new LyException(ExceptionEnum.INVALID_USERNAME_PASSWORD);
        }

    }

    public UserInfo verify(HttpServletRequest request, HttpServletResponse response) {
        try {
            // 获取cookie
            String token = CookieUtils.getCookieValue(request, prop.getUser().getCookieName());
            // 解析token
            Payload<UserInfo> infoFromToken = JwtUtils.getInfoFromToken(token, prop.getPublicKey(), UserInfo.class);
            // =========================黑名单校验开始====================================
            // 如果缓存中有这个标记，说明这个token是无效的
            Boolean boo = redisTemplate.hasKey(infoFromToken.getId());
            if(boo!=null && boo){
                throw new LyException(ExceptionEnum.UNAUTHORIZED);
            }
            // =========================黑名单校验开始====================================
            //============续签开始==========================================
            // 获取token的失效时间
            Date expiration = infoFromToken.getExpiration();
            // 我们让失效时间减去10分钟
            DateTime dateTime = new DateTime(expiration).minusMinutes(prop.getUser().getRefreshTime());
            // 失效时间在10分钟之内，我们重新生成token： 续签
            if(dateTime.isBeforeNow()){
                // 生成token
                String newToken = JwtUtils.generateTokenExpireInMinutes(infoFromToken.getUserInfo(), prop.getPrivateKey(), prop.getUser().getExpire());
                // 写入cookie
                CookieUtils.newCookieBuilder()
                        .name(prop.getUser().getCookieName())
                        .value(newToken)
                        .httpOnly(true)
                        .domain(prop.getUser().getCookieDomain())
                        .maxAge(prop.getUser().getMaxAge())
                        .path(prop.getUser().getPath())
                        .response(response)
                        .build();
            }
            //============续签结束==========================================
            // 返回用户信息
            return infoFromToken.getUserInfo();
        } catch (Exception e) {
            log.error("校验用户token失败！",e);
            throw new LyException(ExceptionEnum.UNAUTHORIZED);
        }
    }


    public void logout(HttpServletRequest request, HttpServletResponse response) {
        // 获取token
        String token = CookieUtils.getCookieValue(request, prop.getUser().getCookieName());
        Payload<Object> payload = null;
        try {
            // 解析token
            payload = JwtUtils.getInfoFromToken(token, prop.getPublicKey());
        } catch (Exception e) {
            // 如果解析token失败直接返回，没必要往下走
            return;
        }
        // 获取token的是过期时间
        Date expiration = payload.getExpiration();
        // 获取剩余的时间毫秒数
        long remainTime = expiration.getTime() - System.currentTimeMillis();
        // 如果减去了当前时间还剩下时间大于3秒中，我们保存到redis的黑名单中
        if(remainTime > 3000){
            // 获取token的id
            String tokenId = payload.getId();
            // 往redis中存入黑名单标记
            redisTemplate.opsForValue().set(tokenId, "1", remainTime, TimeUnit.MILLISECONDS);
        }
        // 删除cookie
        CookieUtils.deleteCookie(prop.getUser().getCookieName(),prop.getUser().getCookieDomain(),response);
    }

    // 颁发token
    public String authorization(Long id, String secret) {
        // 通过后台查询微服务信息：ApplicationInfo
        ApplicationInfo applicationInfo = applicationInfoMapper.selectByPrimaryKey(id);
        // 数据库中没有这个微服务抛异常
        if(applicationInfo == null){
            throw new LyException(ExceptionEnum.UNAUTHORIZED);
        }
        // 如果已经存在这个微服务，我们要校验密码
        if(!passwordEncoder.matches(secret, applicationInfo.getSecret())){
            throw new LyException(ExceptionEnum.UNAUTHORIZED);
        }
        // 执行到这里，说明通过验证，封装载荷
        AppInfo appinfo = new AppInfo();
        appinfo.setId(id);
        appinfo.setServiceName(applicationInfo.getServiceName());
        // 查询出id对应的targetId
        appinfo.setTargetList(applicationInfoMapper.queryTargetIdList(id));
        return JwtUtils.generateTokenExpireInMinutes(appinfo, prop.getPrivateKey(), prop.getApp().getExpire());
    }
}
